package util;

import com.alibaba.druid.pool.DruidDataSource;

import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.Properties;

/**
 * @author Administrator
 * @version 4.0
 * @date 2024/8/1 0001
 * @description JDBC连接数据库的过程，其中加载驱动 获取连接 与后面的释放资源重复执行
 * 创建工具类将这些重复执行的代码进行封装
 * ------------------------------------------------------
 * 配置参数写死在了代码中 不利于代码的动态性 引入properties配置文件 将配置抽离  实现动态修改配置文件功能
 * ------------------------------------------------------
 * 通过druid连接池进行数据库连接的管理  减少由于频繁创建销毁连接造成的资源损失
 * ------------------------------------------------------
 * 提供添加 修改 删除操作统一方法  方便对应sql的快速执行
 * 1)单条dml语句执行方法
 * 2)事务dml语句执行方法
 * ------------------------------------------------------
 * 提供查询方法，传入sql基于ORM对象关系映射，返回自定类型的集合存储返回的数据
 */
public class JDBCUtil {
    //创建连接池变量保存创建的连接池
    public static DruidDataSource ds;

    static {
        try {
            //配置文件仅需要读取一次 所以书写在静态代码块中
//            new FileInputStream(new File("//"));
//            D:\各种文件\Java全栈\Java基础\JavaWeb\maven0826\homework0826\src\main\java\jdbc.properties
            InputStream resourceAsStream = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
            Properties p = new Properties();
            //为了防止每次调用连接方法都执行一次加载 使用静态代码块执行一次
            p.load(resourceAsStream);

            //连接池仅需要创建一次 所以书写在静态代码块中
            ds = new DruidDataSource();
            ds.setUrl(p.getProperty("jdbc.url"));
            ds.setDriverClassName(p.getProperty("jdbc.driver"));
            ds.setUsername(p.getProperty("jdbc.username"));
            ds.setPassword(p.getProperty("jdbc.password"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取连接方法 根据固定的参数返回对应的数据库连接对象
     *
     * @return 数据库连接对象
     */
    public static Connection getConnection() {
        Connection con = null;
        try {
            //con = DriverManager.getConnection(url, username, password);
            //将获取连接代码修改为从连接池获取连接
            con = ds.getConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return con;
    }

    //分别书写关闭方法
    public static void close(Connection con) {
        try {
            if (con != null) {
                con.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    private static void close(Statement statement) {
        try {
            if (statement != null) {
                statement.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    private static void close(ResultSet rs) {
        try {
            if (rs != null) {
                rs.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    //根据实际需要关闭的内容进行关闭
    public static void close(Connection con, Statement statement) {
        close(con);
        close(statement);
    }

    public static void close(Connection con, Statement statement, ResultSet rs) {
        close(con);
        close(statement);
        close(rs);
    }


    /**
     * 单条dml语句执行方法 将传入的dml语句进行执行 通过不确定参数传入数据
     *
     * @param sql  需要执行的dml语句
     * @param objs 基于sql中占位符传入对应的数据
     * @return 数据影响条数
     */
    public static int dml(String sql, Object... objs) {
        Connection connection = getConnection();
        PreparedStatement preparedStatement = null;
        int count = 0;
        try {
            preparedStatement = connection.prepareStatement(sql);
            //不确定参数本质就是数组 遍历数组
            for (int i = 0; i < objs.length; i++) {
                preparedStatement.setObject(i + 1, objs[i]);
            }
            count = preparedStatement.executeUpdate();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            close(connection, preparedStatement);
        }
        return count;
    }

    /**
     * 如果想实现事务需要保证sql在同一个连接执行
     * 注意 当前执行需要进行事务处理 所以在方法中不能使用try catch进行异常处理 否则sql语句执行错误 不会被检测到
     *
     * @param connection 用于执行事务的同一个连接
     * @param sql        执行的dml语句
     * @param objs
     */
    public static void dml(Connection connection, String sql, Object... objs) throws SQLException {
        PreparedStatement preparedStatement = null;
        int count = 0;
        //开启事务
        connection.setAutoCommit(false);
        preparedStatement = connection.prepareStatement(sql);
        //不确定参数本质就是数组 遍历数组
        for (int i = 0; i < objs.length; i++) {
            preparedStatement.setObject(i + 1, objs[i]);
        }
        count = preparedStatement.executeUpdate();
        close(preparedStatement);
    }

    /**
     * 基于ORM对象关系映射
     *
     * @param sql  执行的查询语句
     * @param c    传入用于存储每条数据的类型的clas对象
     * @param objs sql中占位符对应的参数
     * @param <E>  基于传入的类型，确定返回集合中存储数据的类型
     * @Return 保存数据的集合
     **/

    public static <E> ArrayList<E> dql(String sql, Class<E> c, Object... objs) {
        Connection connection = getConnection();
        PreparedStatement pst = null;
        ResultSet resultSet = null;
        //创建集合存储返回数据
        ArrayList<E> list = new ArrayList<>();

        try {
            pst = connection.prepareStatement(sql);
            for (int i = 0; i < objs.length; i++) {
                pst.setObject(i + 1, objs[i]);
            }
            resultSet = pst.executeQuery();

            //获取根据固定的参数类型数据构造函数对象
            Constructor<E> declaredConstructor = c.getDeclaredConstructor();
            //设置构造函数的权限，暴力调用
            declaredConstructor.setAccessible(true);
            //通过反射获取class对象代表类型的所有属性
            Field[] declaredFields = c.getDeclaredFields();
            while (resultSet.next()) {
                //循环每执行一次 代表有一个对象存入集合
                E e = declaredConstructor.newInstance();
                //遍历属性数组
                for (Field declaredField : declaredFields) {
                    //属性的名字就是列名
                    String colName = declaredField.getName();
                    Object colValue = null;
                    try {
                        colValue = resultSet.getObject(colName);
                    } catch (Exception x) {
                    }
                    declaredField.setAccessible(true);
                    declaredField.set(e, colValue);
                }
                list.add(e);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            close(connection, pst, resultSet);
        }
        return list;
    }

}